﻿namespace HttpContextBodyToStreamContentDemo.Api.Middlewares;

public class ProxyMiddleware
{
    private const string CDN_HEADER_NAME = "Cache-Control";
    private static readonly string[] NotForwardedHttpHeaders = new[] { "Connection", "Host" };
    private readonly RequestDelegate _next;
    private readonly IHttpClientFactory _httpClientFactory;

    public ProxyMiddleware(RequestDelegate next, IHttpClientFactory httpClientFactory)
    {
        _next = next;
        _httpClientFactory = httpClientFactory;
    }

    public async Task Invoke(HttpContext context, IUrlRewriter urlRewriter, ProxyHttpClient proxyHttpClient)
    {
        using var httpClient = _httpClientFactory.CreateClient();
        var requestMessage1 = new HttpRequestMessage();
        requestMessage1.Content = new StreamContent(context.Request.Body);
        requestMessage1.RequestUri = new Uri("https://localhost:7132/Order");
        requestMessage1.Headers.Host = new Uri("https://localhost:7132/Order").Host;
        requestMessage1.Method = GetMethod(context.Request.Method);
        foreach (var header in context.Request.Headers)
        {
            if (!NotForwardedHttpHeaders.Contains(header.Key))
            {
                if (header.Key != "User-Agent")
                {
                    if (header.Key == "Content-Type")
                    {
                        if (!requestMessage1.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()) && requestMessage1.Content != null)
                        {
                            requestMessage1.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
                        }
                    }
                }
                //else
                //{
                //    string userAgent = header.Value.Count > 0 ? (header.Value[0] + " " + context.TraceIdentifier) : string.Empty;

                //    if (!requestMessage1.Headers.TryAddWithoutValidation(header.Key, userAgent) && requestMessage1.Content != null)
                //    {
                //        requestMessage1.Content?.Headers.TryAddWithoutValidation(header.Key, userAgent);
                //    }
                //}
            }
        }
        using (var responseMessage = await httpClient.SendAsync(requestMessage1, HttpCompletionOption.ResponseHeadersRead, context.RequestAborted))
        {
            context.Response.StatusCode = (int)responseMessage.StatusCode;
        }

        var targetUri = await urlRewriter.RewriteUri(context);
        if (targetUri == null)
        {
            await _next(context);
            return;
        }

        var requestMessage = GenerateProxifiedRequest(context, targetUri);
        await SendAsync(context, requestMessage, proxyHttpClient);
    }

    private static HttpRequestMessage GenerateProxifiedRequest(HttpContext context, Uri targetUri)
    {
        var requestMessage = new HttpRequestMessage();
        CopyRequestContentAndHeaders(context, requestMessage);
        requestMessage.RequestUri = targetUri;
        requestMessage.Headers.Host = targetUri.Host;
        requestMessage.Method = GetMethod(context.Request.Method);
        return requestMessage;
    }

    private static void CopyRequestContentAndHeaders(HttpContext context, HttpRequestMessage requestMessage)
    {
        var requestMethod = context.Request.Method;

        if (!HttpMethods.IsGet(requestMethod) &&
            !HttpMethods.IsHead(requestMethod) &&
            !HttpMethods.IsDelete(requestMethod) &&
            !HttpMethods.IsTrace(requestMethod))
        {
            var streamContent = new StreamContent(context.Request.Body);
            requestMessage.Content = streamContent;
        }

        foreach (var header in context.Request.Headers)
        {
            if (!NotForwardedHttpHeaders.Contains(header.Key))
            {
                if (header.Key != "User-Agent")
                {
                    if (!requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()) && requestMessage.Content != null)
                    {
                        requestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
                    }
                }
                else
                {
                    string userAgent = header.Value.Count > 0 ? (header.Value[0] + " " + context.TraceIdentifier) : string.Empty;

                    if (!requestMessage.Headers.TryAddWithoutValidation(header.Key, userAgent) && requestMessage.Content != null)
                    {
                        requestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, userAgent);
                    }
                }
            }
        }
    }

    private static HttpMethod GetMethod(string method)
    {
        if (HttpMethods.IsDelete(method)) return HttpMethod.Delete;
        if (HttpMethods.IsGet(method)) return HttpMethod.Get;
        if (HttpMethods.IsHead(method)) return HttpMethod.Head;
        if (HttpMethods.IsOptions(method)) return HttpMethod.Options;
        if (HttpMethods.IsPost(method)) return HttpMethod.Post;
        if (HttpMethods.IsPut(method)) return HttpMethod.Put;
        if (HttpMethods.IsTrace(method)) return HttpMethod.Trace;
        return new HttpMethod(method);
    }

    private async Task SendAsync(HttpContext context, HttpRequestMessage requestMessage, ProxyHttpClient proxyHttpClient)
    {
        using (var responseMessage = await proxyHttpClient.Client.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, context.RequestAborted))
        {
            context.Response.StatusCode = (int)responseMessage.StatusCode;

            foreach (var header in responseMessage.Headers)
            {
                context.Response.Headers[header.Key] = header.Value.ToArray();
            }

            foreach (var header in responseMessage.Content.Headers)
            {
                context.Response.Headers[header.Key] = header.Value.ToArray();
            }

            context.Response.Headers.Remove("transfer-encoding");

            if (!context.Response.Headers.ContainsKey(CDN_HEADER_NAME))
            {
                context.Response.Headers.Add(CDN_HEADER_NAME, "no-cache, no-store");
            }

            await responseMessage.Content.CopyToAsync(context.Response.Body);
        }
    }
}

public interface IUrlRewriter
{
    Task<Uri> RewriteUri(HttpContext context);
}

public class PrefixRewriter : IUrlRewriter
{
    private readonly PathString _prefix; //前缀值
    private readonly string _newHost; //转发的地址

    public PrefixRewriter(PathString prefix, string newHost)
    {
        _prefix = prefix;
        _newHost = newHost;
    }

    public Task<Uri> RewriteUri(HttpContext context)
    {
        if (context.Request.Path.StartsWithSegments(_prefix))//判断访问是否含有前缀
        {
            var newUri = context.Request.Path.Value.Remove(0, _prefix.Value.Length) + context.Request.QueryString;
            var targetUri = new Uri(_newHost + "/order");
            return Task.FromResult(targetUri);
        }

        return Task.FromResult((Uri)null);
    }
}

public class ProxyHttpClient
{
    public HttpClient Client { get; private set; }

    public ProxyHttpClient(HttpClient httpClient)
    {
        Client = httpClient;
    }
}